#include <Infrastructure/Container/BufferQueue.h>

#include <memory.h>
#include <xutility>

namespace Infra
{

// This structure is a complete node in the queue
struct BufferQueue::Node
{
	// The max size of this node's data
	size_t maxSize;
	// Linked list refs
	Node* prev;
	Node* next;
	// Flags for reuse
	bool doneWrite;
	bool doneRead;
	// Raw data
	char data[ 1 ];
	Node* reset( void )
	{
		prev = 0;
		next = 0;
		doneWrite = false;
		doneRead = false;
		return this;
	}
};

BufferQueue::BufferQueue( size_t chunkSize, const size_t sizeLimit )
: m_chunkSize( chunkSize )
, m_sizeLimit( sizeLimit )
, m_root( 0 )
, m_writeNode( 0 )
, m_writeOffset( 0 )
, m_readNode( 0 )
, m_readOffset( 0 )
, m_busyNode( false )
{
	::memset( &m_stats, 0, sizeof( m_stats ) );
}

BufferQueue::~BufferQueue( void )
{
	boost::mutex::scoped_lock lock( m_guard );
	internalClear();
	for ( size_t i = 0; i < m_spares.size(); ++i )
	{
		deleteNode( m_spares[ i ] );
	}
}

void
BufferQueue::internalClear( void )
{
	Node* node = m_root;
	while ( node )
	{
		Node* temp = node->next;
		deleteNode( node );
		node = temp;
	}
	
	m_root = 0;
	m_writeNode = 0;
	m_writeOffset = 0;
	m_readNode = 0;
	m_readOffset = 0;

	::memset( &m_stats, 0, sizeof( m_stats ) );
}

bool
BufferQueue::push( const char* data, size_t len, size_t userDataLen, const void* userData, Setter setter )
{
	Node* writeTo = 0;

	if ( ( m_stats.liveSize + len + userDataLen ) > m_sizeLimit ) return false;

	// If the data fits in this node then just write
	if ( m_writeNode && ( m_writeOffset + sizeof( size_t ) + userDataLen + len ) <= m_writeNode->maxSize )
	{
		writeTo = m_writeNode;
	}

	// Get a new node if we need it
	else if ( ( writeTo = getNode( sizeof( size_t ) + userDataLen + len ) ) != 0 )
	{
		if ( m_writeNode )
		{
			// Link it to the end
			writeTo->prev = m_writeNode;
			m_writeNode->next = writeTo;

			// Mark the old node
			m_writeNode->doneWrite = true;
		}
		// Reset position
		m_writeOffset = 0;
		m_writeNode = writeTo;
	}

	// Make certain we have a starting point
	if ( m_root == 0 ) m_root = m_writeNode;

	++m_stats.push;
	m_stats.liveSize += ( len + userDataLen );

	// Write Data Len
	*( size_t* )( m_writeNode->data + m_writeOffset ) = len;
	m_writeOffset += sizeof( size_t );

	// Write User Out
	if ( setter && data ) setter( ( m_writeNode->data + m_writeOffset ), userData );
	m_writeOffset += userDataLen;

	// Write Data
	if ( data && len )
	{
		::memcpy( m_writeNode->data + m_writeOffset, data, len );
	}
	m_writeOffset += len;

	// Mark the next block as empty
	if ( m_writeOffset + sizeof( size_t ) + userDataLen < m_writeNode->maxSize )
	{
		*( size_t* )( m_writeNode->data + m_writeOffset ) = -1;
	}

	// If we can't, mark node as done
	else
	{
		m_writeNode->doneWrite = true;
	}
	return true;
}

BufferQueue::Out<>
BufferQueue::pop( size_t userDataLen )
{
	Out<> data;

	// Make certain we have a node
	if ( m_readNode == 0 && m_root && m_root->doneRead == false )
	{
		m_readNode = m_root;
	}

	// Is there anything
	if ( m_readNode == 0 )
	{
		return data;
	}

	// We know we are at the end
	if ( ( ( m_readOffset + sizeof( size_t ) + userDataLen ) > m_readNode->maxSize ) )
	{
		m_readNode->doneRead = true;
		// Get rid of it
		popSkip();
		// And start over
		return pop( userDataLen );
	}

	size_t len = *( size_t* )( m_readNode->data + m_readOffset );

	// If we reached the end of this one
	if ( len == -1 && m_readNode->next )
	{
		m_readNode->doneRead = true;
		// Get rid of it
		popSkip();
		// And start over
		return pop( userDataLen );
	}
	// else read if there is data
	else if ( len != -1 )
	{
		++m_stats.pop;
		m_stats.liveSize -= ( len + userDataLen );

		// Set the len
		data.len = len;
		m_readOffset += sizeof( size_t );

		// Set the user data
		if ( userDataLen )
		{
			data.userData = m_readNode->data + m_readOffset;
			m_readOffset += userDataLen;
		}

		// Set the data
		if ( data.len )
		{
			data.data = m_readNode->data + m_readOffset;
			m_readOffset += data.len;
		}
	}
	return data;
}

void
BufferQueue::popSkip( void )
{
	// If node is done
	if ( m_readNode->doneRead == true )
	{
		Node* thisNode = m_readNode;

		// Reset the read/write nodes
		m_readOffset = 0;
		m_readNode = thisNode->next;

		// This node has been exhausted
		if ( thisNode->doneWrite == true )
		{
			// Extract it
			if ( thisNode->prev ) thisNode->prev->next = thisNode->next;
			if ( thisNode->next ) thisNode->next->prev = thisNode->prev;

			// Make certain m_writeNode is still valid
			if ( thisNode == m_writeNode )
			{
				m_writeOffset = 0;
				m_writeNode = m_readNode;
			}

			// Make certain m_root is still valid
			if ( thisNode == m_root )
			{
				m_root = m_readNode;
			}

			// Recoird an empty queue
			if ( m_root == 0 )
			{
				++m_stats.emptied;
			}

			// And get rid of it
			junkNode( thisNode );
		}
	}
}

std::pair< BufferQueue::Out<>, BufferQueue::Node* > 
BufferQueue::peek( size_t userDataLen )
{
	std::pair< Out<>, Node* > result;
	result.second = 0;

	Node* readNode = m_readNode;
	size_t readOffset = m_readOffset;

	if ( m_readNode == 0 && m_root && m_root->doneRead == false )
	{
		readNode = m_root;
	}

	if ( readNode == 0 )
	{
		return result;
	}

	if ( ( ( readOffset + sizeof( size_t ) + userDataLen ) > readNode->maxSize ) )
	{
		readOffset = 0;
		readNode = readNode->next;
		if ( readNode == 0 ) return result;
	}

	size_t len = *( size_t* )( readNode->data + readOffset );

	if ( len == -1 && readNode->next )
	{
		readOffset = 0;
		readNode = readNode->next;
		if ( readNode == 0 ) return result;
	}

	else if ( len != -1 )
	{
		result.second = readNode;
		result.first.len = len;
		readOffset += sizeof( size_t );
		if ( userDataLen )
		{
			result.first.userData = readNode->data + readOffset;
			readOffset += userDataLen;
		}
		if ( len )
		{
			result.first.data = readNode->data + readOffset;
			readOffset += len;
		}
	}
	return result;
}

BufferQueue::Node*
BufferQueue::getNode( size_t size )
{
	// Find a spare if we can
	if ( m_spares.empty() == false )
	{
		if ( size <= m_chunkSize )
		{
			++m_stats.reuseNode;
			return m_spares.pop_back()->reset();
		}
		else if ( size <= m_spares.front()->maxSize )
		{
			// We may want to loop - but requires erase in container
			++m_stats.reuseNode;
			return m_spares.pop_front()->reset();
		}
	}
	// Nothing found - so allocate
	++m_stats.allocNode;
	size_t maxSize = m_chunkSize;
	if ( size > m_chunkSize )
	{
		++m_stats.pushAboveChunk;
		maxSize = size;
	}
	// Locate enough for node data, raw data, and structure alignment
	Node* node = reinterpret_cast< Node* >( new char[ maxSize + sizeof( Node ) + 8 ] );
	node->maxSize = maxSize;
	return node->reset();
}

void
BufferQueue::junkNode( Node* node )
{
	++m_stats.discardNode;
	// Try to push regular sized one on the back
	if ( node->maxSize == m_chunkSize )
	{
		if ( m_spares.full() == true )
		{
			++m_stats.discardNode;
			deleteNode( node );
		}
		else
		{
			m_spares.push_back( node );
		}
	}
	// Try to put bigger ones on the front
	else
	{
		if ( m_spares.full() == false )
		{
			m_spares.push_front( node );
		}
		else if ( node->maxSize <= m_spares.front()->maxSize )
		{
			++m_stats.discardNode;
			deleteNode( node );
		} 
		else if ( node->maxSize <= m_spares.back()->maxSize )
		{
			++m_stats.discardNode;
			deleteNode( node );
		}
		else
		{
			++m_stats.discardNode;
			deleteNode( m_spares.back() );
			m_spares.push_front( node );
		}
	}
}

struct UntilIsNull
{
	UntilIsNull( BufferQueue::Node*& node ) : m_node( node ){}
	bool operator () ( void ) const { return m_node==0; }
	BufferQueue::Node*& m_node;
};

void
BufferQueue::deleteNode( Node* node )
{
	if ( node == 0 ) return;
	{
		boost::mutex::scoped_lock lock( m_guard );
		if ( m_busyNode == node )
		{
			m_readCondition.wait( lock, UntilIsNull( m_busyNode ) );
		}
	}
	delete[] reinterpret_cast< char* >( node );
}

}
